<?php

namespace app\common\service;

use Carbon\Carbon;
use think\facade\Log;
use app\common\model\Config;
use app\common\model\Payment;
use app\common\util\EnvHelper;
use app\common\service\Service;
use EasyWeChat\Pay\Application;
use app\common\model\PaymentRefund;

/**
 * 微信支付相关V3
 */
class WechatPayV3Service extends Service
{
    /**
     * $app 微信支付实例
     *
     * @var Application
     */
    protected $application;

    protected $wechatConfig;

    /**
     * 微信支付配置标识
     */
    protected $code;

    public function __construct($app, $code = Config::CODE_WECHAT)
    {
        parent::__construct($app);
        $this->wechatConfig = $config = Config::getConfigContent($code);
        if (empty($config)) {
            throw $this->exception('微信配置为空，无法使用微信支付相关功能');
        }
        // easywechat 微信支付配置
        $app_config = [
            // app id
            'app_id' => $config->app_id,
            // 商家号
            'mch_id' => (int) $config->mch_id,

            // v3API 密钥
            'secret_key' => $config->v3key ?? $config->key,
            // v3商户证书私钥
            'private_key' => $config->cert_path . 'apiclient_key.pem',
            // v3商户证书公钥
            'certificate' => $config->cert_path . 'apiclient_cert.pem',

            // v2API 密钥
            'v2_secret_key' => $config->key,
            // v2密钥路径
            'key_path' => $config->cert_path . 'apiclient_key.pem',
            // v2证书路径
            'cert_path' => $config->cert_path . 'apiclient_cert.pem',

            // 平台证书：微信支付 APIv3 平台证书，需要使用工具下载
            // 下载工具：https://github.com/wechatpay-apiv3/CertificateDownloader
            'platform_certs' => [
                // 请使用绝对路径
                $config->cert_path . 'platfrom_cert.pem',
            ],

            /**
             * 接口请求相关配置，超时时间等，具体可用参数请参考：
             * https://github.com/symfony/symfony/blob/5.3/src/Symfony/Contracts/HttpClient/HttpClientInterface.php
             */
            'http' => [
                'throw' => true,
                // 状态码非 200、300 时是否抛出异常，默认为开启
                'timeout' => 10.0, // TODO 测试服会有很严重的请求缓慢问题，随意这里把超时设置长一点，之后这里改成判断环境修改超时时间
                // 'base_uri' => 'https://api.mch.weixin.qq.com/', // 如果你在国外想要覆盖默认的 url 的时候才使用，根据不同的模块配置不同的 uri
            ],
        ];

        $this->application = new Application($app_config);
        $this->code = $code;
    }

    /**
     * 通过payment对象进行支付并获取prepayId到payment中
     *
     * @param Payment $payment
     * 
     * @return ?Payment 修改prepay_id后的Payment
     */
    public function queryJsApiByPayment(?Payment $payment)
    {
        // 无需支付
        if (empty($payment) || $payment->isEmpty()) {
            return null;
        }
        $url = url('Notify/wechat', [], false, true)->build();
        // v3版本的微信支付只能使用https，为了保证开发环境可以正常测试支付，这里替换一下
        if (EnvHelper::getReleaseType() !== EnvHelper::RELEASE_TYPE_PROD) {
            $url = str_replace('http:', 'https:', $url);
        }
        $openid = $payment?->member?->wxuser?->openid;
        if (!$openid) {
            throw $this->exception('用户未绑定openid');
        }
        $requestBody = [
            // 商户id
            'mchid' => (string) $this->application->getMerchant()->getMerchantId(),
            // 商户订单号
            'out_trade_no' => $payment->out_trade_no,
            // 订单失效时间，设为1天
            'time_expire' => Carbon::now()->addDays()->toISOString(),
            // appid
            'appid' => $this->wechatConfig->app_id,
            // 订单详情
            'description' => '云门商圈-' . isset($payment->orders[0]) ? $payment->orders[0]->product->getAttr('name') : '产品',
            // 回调url
            'notify_url' => $url,
            // 金额
            'amount' => [
                // 总金额，分为单位
                'total' => intval(round($payment->total_fee * 100)),
                // 货币
                'currency' => 'CNY'
            ],
            // 支付人
            'payer' => [
                'openid' => $openid
            ]
        ];
        $payment->content = json_encode($requestBody);
        $payment->save();

        Log::info('微信支付参数：' . print_r($requestBody, true));
        $response = $this->getClient()->postJson('v3/pay/transactions/jsapi', $requestBody);

        $body = $response->toArray(false);
        Log::info('微信支付响应：' . print_r($body, true));
        if ($response->getStatusCode() != 200) {
            throw $this->exception("支付失败[{$body['code']}]: {$body['message']}");
        }

        $payment->prepay_id = $body['prepay_id'];
        $payment->result_code = Payment::RESULT_SUCCESS;
        $payment->save();
        return $payment;
    }

    /**
     * 通过PaymentRefund进行微信退订及记录部分必要信息，不涉及其他业务需求，通过refund()方法实现
     * 
     * @param PaymentRefund $refund 微信支付退款对象，out_refund_no不能为空
     * 
     * @return PaymentRefund 微信支付退款对象
     */
    public function refundByPaymentRefund(?PaymentRefund $refund)
    {
        $payment = $refund->payment;

        if (empty($refund->out_refund_no)) {
            throw $this->exception('PaymentRefund退款单号不能为空');
        }

        // 进行微信退款
        $response = $this->refund(
            outTradeNo: $payment->out_trade_no,
            outRefundNo: $refund->out_refund_no,
            refund: intval(round($refund->money * 100)),
            total: intval(round($payment->total_fee * 100))
        );
        $refund->is_refund = 1;

        // 修改退款状态
        if ($response['status'] == 'SUCCESS') {
            $refund->result = PaymentRefund::RESULT_SUCCESS;
        } elseif ($response['status'] == 'PROCESSING') {
            $refund->result = PaymentRefund::RESULT_PROCESSING;
        } else {
            $refund->result = PaymentRefund::RESULT_FAILED;
        }

        $refund->save();
        return $refund;
    }

    /**
     * 退款，这是一个单纯的退款接口，不包含业务内容
     * 这个项目是php8，所以你可以使用命名参数进行对这个函数传参https://www.php.net/manual/zh/functions.arguments.php#functions.named-arguments
     * 参数错误会自动抛出异常，无需进行业务处理，但是要进行事务回滚
     * v3版本支持部分退款，如果你需要使用该功能，请谨慎处理
     * 
     * @param string $reason 退款原因
     * @param string $outTradeNo 商户订单号
     * @param string $outRefundNo 商户退款单号, 建议使用generateSnowFlaskId('rf')生成
     * @param int $refund 退款总金额，单位为分，只能为整数，详见支付金额
     * @param int $total 原支付交易的订单总金额，单位为分，只能为整数
     *
     * @return array 参考返回值https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/create.html
     */
    public function refund(string $outTradeNo, string $outRefundNo, int $refund, int $total, string $reason = '退款', ?string $notifyUrl = '')
    {
        if (!$notifyUrl) {
            $notifyUrl = url('notify/refund', [], false, true);
            // v3版本的微信支付只能使用https，为了保证开发环境可以正常测试退款，这里替换一下
            if (EnvHelper::getReleaseType() !== EnvHelper::RELEASE_TYPE_PROD) {
                $notifyUrl = str_replace('http', 'https', $notifyUrl);
            }
        }
        if ($refund <= 0) {
            throw $this->exception('金额错误，应大于1分钱');
        }

        $requestBody = [
            'out_trade_no' => $outTradeNo,
            'reason' => $reason,
            'out_refund_no' => $outRefundNo,
            'amount' => [
                'refund' => $refund,
                //【退款金额】 退款总金额，单位为分，只能为整数，详见支付金额
                'total' => $total,
                //【原订单金额】 原支付交易的订单总金额，单位为分，只能为整数。
                'currency' => 'CNY' // 【退款币种】 符合ISO 4217标准的三位字母代码，目前只支持人民币：CNY。
            ]
        ];
        Log::info('微信退款参数：' . print_r($requestBody, true));
        $response = $this->getClient()->postJson('v3/refund/domestic/refunds', $requestBody);

        $body = $response->toArray(false);
        Log::info('微信退款响应：' . print_r($body, true));
        if ($response->getStatusCode() != 200) {
            throw $this->exception("退款失败[{$body['code']}]: {$body['message']}");
        }

        return $body;
    }

    /**
     * 通过outTradeNo查询微信订单
     *
     * @param mixed $outTradeNo 商户订单号
     * 
     * @return array 参考https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/query-by-out-trade-no.html
     */
    public function queryOrderByOutTradeNo(string $outTradeNo)
    {
        $response = $this->getClient()->get("v3/pay/transactions/out-trade-no/{$outTradeNo}", [
            'query' => [
                'mchid' => $this->application->getMerchant()->getMerchantId()
            ]
        ]);
        
        $body = $response->toArray(false);
        Log::info("微信查询订单响应：" . json_encode($body, JSON_UNESCAPED_UNICODE));
        if ($response->getStatusCode() != 200) {
            throw $this->exception("查询订单异常[{$body['code']}]: {$body['message']}");
        }

        return $body;
    }

    public function queryOrderByTransactionId(string $transactionId)
    {

    }

    public function getBridgeConfig(string $prepayId)
    {
        // 无需支付
        if (empty($prepayId)) {
            return [];
        }
        return $this->getUtils()->buildBridgeConfig($prepayId, $this->wechatConfig->app_id);
    }

    public function getApp(): Application
    {
        return $this->application;
    }
    public function getClient()
    {
        return $this->application->getClient();
    }

    public function getUtils()
    {
        return $this->application->getUtils();
    }

    public function getServer()
    {
        return $this->application->getServer();
    }

}